﻿#region << 版 本 注 释 >>
/*----------------------------------------------------------------
 * 版权所有 (c) 2022 北京超维景生物科技有限公司 保留所有权利。
 * CLR版本：4.0.30319.42000
 * 文件名：Kernel
 * 
 * 创建者：huangyang
 * 电子邮箱：huangyang@tvscope.cn
 * 创建时间：2023/2/10 14:09:35
 * 版本：V1.0.0
 * 描述：
 *
 * ----------------------------------------------------------------
 * 修改人：
 * 时间：
 * 修改说明：
 *
 * 版本：V1.0.1
 *----------------------------------------------------------------*/
#endregion << 版 本 注 释 >>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ImageK.IO;
using ImageK.Java;
using ImageK.Plugin;
using ImageK.Process;
using ImageK.Util;

namespace ImageK
{
    /// <summary>
    /// This class represents an array of disk-resident images.
    /// </summary>
    public class VirtualStack:ImageStack
    {
        private const int INITIAL_SIZE = 100;
        private string path;
        private int nSlices;
        private string[] names;
        private string[] labels;
        private int bitDepth;
        private int delay;
        private Dictionary<string,object> properties;
        private bool generateData;
        private int[] indexes;  // used to translate non-CZT hyperstack slice numbers

        /** Default constructor. */
        public VirtualStack() { }

        public VirtualStack(int width, int height):base(width,height)
        {
        }

        /** Creates an empty virtual stack.
         * @param width		image width
         * @param height	image height
         * @param cm	ColorModel or null
         * @param path	file path of directory containing the images
         * @see #addSlice(String)
         * @see <a href="http://wsr.imagej.net/macros/js/OpenAsVirtualStack.js">OpenAsVirtualStack.js</a>
        */
        public VirtualStack(int width, int height, ColorModel cm, string path):base(width,height,cm)
        {
            path = PathUtil.AddSeparator(path);
            this.path = path;
            names = new string[INITIAL_SIZE];
            labels = new string[INITIAL_SIZE];
        }

        /** Creates a virtual stack with no backing storage.<br>
         * See: Help&gt;Examples&gt;JavaScript&gt;Terabyte VirtualStack
        */
        public VirtualStack(int width, int height, int slices): this(width, height, slices, "8-bit")
        {
            
        }

        /** Creates a virtual stack with no backing storage.<br>
         * See: Help&gt;Examples&gt;JavaScript&gt;Terabyte VirtualStack
         */
        public VirtualStack(int width, int height, int slices, String options): base(width, height, null)
        {
            
            nSlices = slices;
            int depth = 8;
            if (options.Contains("16-bit")) depth=16;
            if (options.Contains("RGB")) depth=24;
            if (options.Contains("32-bit")) depth=32;
            if (options.Contains("delay")) delay=250;
            this.generateData = options.Contains("fill");
            this.bitDepth = depth;
        }

        /** Adds an image to the end of a virtual stack created using the
         * VirtualStack(w,h,cm,path) constructor. The argument 
         * can be a full file path (e.g., "C:/Users/wayne/dir1/image.tif")
         * if the 'path' argument in the constructor is "". File names
         * that start with '.' are ignored.
        */
        public void addSlice(string fileName)
        {
            if (fileName==null)
                throw new ArgumentException("'fileName' is null!");
            if (fileName.StartsWith("."))
                return;
            if (names==null)
                throw new ArgumentException("VirtualStack(w,h,cm,path) constructor not used");
            nSlices++;
            if (nSlices==names.Length)
            {
                String[] tmp = new String[nSlices*2];
                System.Array.Copy(names, 0, tmp, 0, nSlices);
                names = tmp;
                tmp = new String[nSlices*2];
                System.Array.Copy(labels, 0, tmp, 0, nSlices);
                labels = tmp;
            }
            names[nSlices-1] = fileName;
        }

        /** Does nothing. */
        public void addSlice(String sliceLabel, Object pixels)
        {
        }

        /** Does nothing.. */
        public void addSlice(String sliceLabel, ImageProcessor ip)
        {
        }

        /** Does noting. */
        public void addSlice(String sliceLabel, ImageProcessor ip, int n)
        {
        }

        /** Deletes the specified slice, where {@literal 1<=n<=nslices}. */
        public void deleteSlice(int n)
        {
            if (n<1 || n>nSlices)
                throw new ArgumentException("Argument out of range: "+n);
            if (nSlices<1)
                return;
            for (int i = n; i<nSlices; i++)
                names[i-1] = names[i];
            names[nSlices-1] = null;
            nSlices--;
        }

        /** Deletes the last slice in the stack. */
        public void deleteLastSlice()
        {
            int n = size();
            if (n>0)
                deleteSlice(n);
        }

        /** Returns the pixel array for the specified slice, where {@literal 1<=n<=nslices}. */
        public Object getPixels(int n)
        {
            ImageProcessor ip = getProcessor(n);
            if (ip!=null)
                return ip.getPixels();
            else
                return null;
        }

        /** Assigns a pixel array to the specified slice, where {@literal 1<=n<=nslices}. */
        public void setPixels(Object pixels, int n)
        {
        }

        /** Returns an ImageProcessor for the specified slice,
         * where {@literal 1<=n<=nslices}. Returns null if
         * the stack is empty.
        */
        public ImageProcessor getProcessor(int n)
        {
            if (path==null)
            {  //Help>Examples?JavaScript>Terabyte VirtualStack
                ImageProcessor ip1 = null;
                int w = getWidth(), h = getHeight();
                switch (bitDepth)
                {
                    case 8: ip1 = new ByteProcessor(w, h); break;
                    case 16: ip1 = new ShortProcessor(w, h); break;
                    case 24: ip1 = new ColorProcessor(w, h); break;
                    case 32: ip1 = new FloatProcessor(w, h); break;
                }
                String hlabel = null;
                if (generateData)
                {
                    int value = 0;
                    ImagePlus img = WindowManager.getCurrentImage();
                    if (img!=null && img.getStackSize()==nSlices)
                        value = img.getCurrentSlice()-1;
                    if (bitDepth==16)
                        value *= 256;
                    if (bitDepth!=32)
                    {
                        for (int i = 0; i<ip1.getPixelCount(); i++)
                            ip1.set(i, value++);
                    }
                    if (img!=null && img.isHyperStack())
                    {
                        int[] pos = img.convertIndexToPosition(n);
                        hlabel = pos[0]+" "+pos[1]+" "+pos[2]+" "+n;
                    }
                }
                label(ip1, hlabel!=null ? hlabel : ""+n, Color.White);
                if (delay>0)
                    IJ.wait(delay);
                return ip1;
            }
            n = translate(n);  // update n for hyperstacks not in the default CZT order
            Opener opener = new Opener();
            opener.setSilentMode(true);
            IJ.redirectErrorMessages(true);
            ImagePlus imp = opener.openTempImage(path, names[n-1]);
            IJ.redirectErrorMessages(false);
            ImageProcessor ip = null;
            int depthThisImage = 0;
            if (imp!=null)
            {
                int w = imp.getWidth();
                int h = imp.getHeight();
                int type = imp.getType();
                ColorModel cm = imp.getProcessor().getColorModel();
                String info = (String)imp.getProperty("Info");
                if (info!=null)
                {
                    if (FolderOpener.useInfo(info))
                        labels[n-1] = info;
                }
                else
                {
                    String sliceLabel = imp.getStack().getSliceLabel(1);
                    if (FolderOpener.useInfo(sliceLabel))
                        labels[n-1] = "Label: "+sliceLabel;
                }
                depthThisImage = imp.getBitDepth();
                ip = imp.getProcessor();
                ip.setOverlay(imp.getOverlay());
                properties = imp.getProperty("FHT")!=null ? imp.getProperties() : null;
            }
            else
            {
                String msg = File.Exists(Path.Combine(path, names[n-1])) ? "Error opening " : "File not found: ";
                ip = new ByteProcessor(getWidth(), getHeight());
                ip.invert();
                label(ip, msg+names[n-1], Color.Black);
                depthThisImage = 8;
            }
            if (depthThisImage!=bitDepth)
            {
                switch (bitDepth)
                {
                    case 8: ip=ip.convertToByte(true); break;
                    case 16: ip=ip.convertToShort(true); break;
                    case 24: ip=ip.convertToRGB(); break;
                    case 32: ip=ip.convertToFloat(); break;
                }
            }
            if (ip.getWidth()!=getWidth() || ip.getHeight()!=getHeight())
            {
                ImageProcessor ip2 = ip.createProcessor(getWidth(), getHeight());
                ip2.insert(ip, 0, 0);
                ip = ip2;
            }
            if (cTable!=null)
                ip.setCalibrationTable(cTable);
            return ip;
        }

        private void label(ImageProcessor ip, String msg, Color color)
        {
            int size = getHeight()/20;
            if (size<9) size=9;
            Font font = new Font("Helvetica", size, FontStyle.Regular);
            ip.setFont(font);
            ip.setAntialiasedText(true);
            ip.setColor(color);
            ip.drawString(msg, size, size*2);
        }

        /** Currently not implemented */
        public int saveChanges(int n)
        {
            return -1;
        }

        /** Returns the number of slices in this stack. */
        public int size()
        {
            return getSize();
        }

        public int getSize()
        {
            return nSlices;
        }

        /** Returns the label of the Nth image. */
        public String getSliceLabel(int n)
        {
            if (labels==null)
                return null;
            String label = labels[n-1];
            if (label==null)
                return names[n-1];
            else
            {
                if (label.StartsWith("Label: "))  // slice label
                    return label.Substring(7, label.Length);
                else
                    return names[n-1]+"\n"+label;
            }
        }

        /** Returns null. */
        public Object[] getImageArray()
        {
            return null;
        }

        /** Does nothing. */
        public void setSliceLabel(String label, int n)
        {
        }

        /** Always return true. */
        public bool isVirtual()
        {
            return true;
        }

        /** Does nothing. */
        public void trim()
        {
        }

        /** Returns the path to the directory containing the images. */
        public String getDirectory()
        {
            return IJ.addSeparator(path);
        }

        /** Returns the file name of the specified slice, where {@literal 1<=n<=nslices}. */
        public String getFileName(int n)
        {
            return names[n-1];
        }

        /** Sets the bit depth (8, 16, 24 or 32). */
        public void setBitDepth(int bitDepth)
        {
            this.bitDepth = bitDepth;
        }

        /** Returns the bit depth (8, 16, 24 or 32), or 0 if the bit depth is not known. */
        public int getBitDepth()
        {
            return bitDepth;
        }

        public ImageStack sortDicom(String[] strings, String[] info, int maxDigits)
        {
            int n = size();
            String[] names2 = new String[n];
            for (int i = 0; i<n; i++)
                names2[i] = names[i];
            for (int i = 0; i<n; i++)
            {
                int slice = (int)Tools.parseDouble(strings[i].Substring(strings[i].Length-maxDigits), 0.0);
                if (slice==0) return null;
                names[i] = names2[slice-1];
                labels[i] = info[slice-1];
            }
            return this;
        }

        /** Returns the ImagePlus Properties assoctated with the current slice, or null. */
        public Dictionary<string,object> getProperties()
        {
            return properties;
        }

        /** Sets the table that translates slice numbers of hyperstacks not in default CZT order. */
        public void setIndexes(int[] indexes)
        {
            this.indexes = indexes;
        }

        /** Translates slice numbers of hyperstacks not in default CZT order. */
        public int translate(int n)
        {
            int n2 = (indexes!=null&&indexes.Length==getSize()) ? indexes[n-1]+1 : n;
            //IJ.log("translate: "+n+" "+n2+" "+getSize()+" "+(indexes!=null?indexes.length:null));
            return n2;
        }

        /** Reduces the number of slices in this stack by a factor. */
        public void reduce(int factor)
        {
            if (factor<2 || nSlices/factor<1 || names==null)
                return;
            nSlices = nSlices/factor;
            for (int i = 0; i<nSlices; i++)
            {
                names[i] = names[i*factor];
                labels[i] = labels[i*factor];
            }
            ImagePlus imp = WindowManager.getCurrentImage();
            if (imp!=null)
            {
                imp.setSlice(1);
                imp.updateAndRepaintWindow();
            }
        }
    }
}
